package cn.kgc.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.kgc.base.BaseInfoProperties;
import cn.kgc.entity.*;
import cn.kgc.feign.UsersFeignClient;
import cn.kgc.form.ResumeUpdateForm;
import cn.kgc.grace.result.GraceJSONResult;
import cn.kgc.mapper.ResumeMapper;
import cn.kgc.service.*;
import cn.kgc.utils.PagedGridResult;
import cn.kgc.utils.RedisUtil;
import cn.kgc.vo.ResumeVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.toolkit.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * @author k
 */
@Slf4j
@Service("resumeService")
@SuppressWarnings("all")
public class ResumeServiceImpl extends ServiceImpl<ResumeMapper, Resume> implements ResumeService {
    @Resource
    private UsersFeignClient usersFeignClient;
    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Resource
    private ResumeWorkExpService resumeWorkExpService;
    @Resource
    private ResumeEducationService resumeEducationService;
    @Resource
    private ResumeExpectService resumeExpectService;
    @Resource
    private ResumeCollectService resumeCollectService;
    @Resource
    private ResumeReadService resumeReadService;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private ResumeProjectExpServiceImpl resumeProjectExpService;


    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Override
    public ResumeVo queryResumeVo(String resumeId) {

        String resumeKey = BaseInfoProperties.REDIS_RESUME_INFO + ":" + resumeId;

        String lockKey = BaseInfoProperties.REDIS_RESUME_LOCK + ":" + resumeId;

        //1. 判断缓存中是否有数据，有则直接返回
        Object object = redisUtil.get(resumeKey);
        //2. 没有则查询数据库，并将查询结果存入缓存 要加一把锁，防止缓存击穿
        if (object != null) {
            return (ResumeVo) object; //3. 返回查询结果
        }
        //没有获得所得请求  如果超过两秒还没有获得锁，则直接返回空的信息
        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (!lock.tryLock(2, 10, TimeUnit.SECONDS)) {
                ResumeVo resumeVo = new ResumeVo();
                return resumeVo; //返回空的信息
            }
            //3.获得锁的请求 再次判断缓存中是否有数据  如果缓存中有数据直接返回  如果缓存没有数据则查询数据库
            object = redisUtil.get(resumeKey);
            if (object != null) {
                return (ResumeVo) object; //3. 返回查询结果
            }
            //4. 如果缓存中没有数据再从数据库中查询数据,需要从多个表中查询完整的数据
            ResumeVo resumeVo = new ResumeVo();
            //4.1  简历的信息
            Resume resume = getById(resumeId);
            //如果数据库中没有简历的信息 讲空的简历信息保存到缓存中返回
            if (resume == null) {
                redisUtil.set(resumeKey, resumeVo, 60 , TimeUnit.MINUTES);
                return resumeVo;
            }
            //将简历的信息保存到resumeVo中
            BeanUtil.copyProperties(resume, resumeVo);

            CompletableFuture<Void> userFuture = CompletableFuture.runAsync(() -> {
                //4.2  用户的基本信息
                Object userObject = usersFeignClient.detail(resume.getUserId());

                GraceJSONResult jsonResult =
                        JSONUtil.toBean(JSONUtil.toJsonStr(userObject), GraceJSONResult.class);

                Users users = JSONUtil.toBean(JSONUtil.toJsonStr(jsonResult.getData()), Users.class);

                resumeVo.setId(resume.getUserId());
                String userName = users.getShowWhichName() == 1 ? users.getRealName() : users.getNickname();
                resumeVo.setUserName(userName);
                resumeVo.setAge(users.getAge());
                if (users.getSex() == 1) {
                    resumeVo.setSex("男");
                } else if (users.getSex() == 0) {
                    resumeVo.setSex("女");
                } else {
                    resumeVo.setSex("保密");
                }
            }, threadPoolTaskExecutor);//异步执行


            //4.3 用户的教育经历
            CompletableFuture<Void> educationsFuture = CompletableFuture.runAsync(() -> {
                List<ResumeEducation> educations = resumeEducationService.list(
                        new LambdaQueryWrapper<ResumeEducation>().eq(ResumeEducation::getResumeId, resumeId)
                );

                resumeVo.setResumeEducations(educations);
            }, threadPoolTaskExecutor);//异步执行


            //4.4 用户的求职意向

            CompletableFuture<Void> expectsFuture = CompletableFuture.runAsync(() -> {
                List<ResumeExpect> expects = resumeExpectService.list(
                        new LambdaQueryWrapper<ResumeExpect>().eq(ResumeExpect::getResumeId, resumeId)
                );
                resumeVo.setResumeExpects(expects);
            }, threadPoolTaskExecutor);//异步执行

            //4.5 用户的工作经验

            CompletableFuture<Void> workExpsFuture = CompletableFuture.runAsync(() -> {
                List<ResumeWorkExp> workExps = resumeWorkExpService.list(
                        new LambdaQueryWrapper<ResumeWorkExp>().eq(ResumeWorkExp::getResumeId, resumeId)
                );
                resumeVo.setResumeWorkExps(workExps);
            }, threadPoolTaskExecutor);//异步执行

            CompletableFuture<Void> projectFuture = CompletableFuture.runAsync(() -> {
                //4.6 用户的项目经验
                List<ResumeProjectExp> projectExps = resumeProjectExpService.list(
                        new LambdaQueryWrapper<ResumeProjectExp>().eq(ResumeProjectExp::getResumeId, resumeId)
                );
                resumeVo.setResumeProjectExps(projectExps);
            }, threadPoolTaskExecutor);

            //等待所有异步任务完成
            CompletableFuture.allOf(userFuture, educationsFuture, workExpsFuture, projectFuture).join();
            //如果数据库中有数据将查询的结果存放到缓存中  返回查询结果
            redisUtil.set(resumeKey, resumeVo,30,TimeUnit.DAYS);
            return resumeVo;
        }catch (Exception e){
            log.error("查询简历失败",e);
            return null;
        }
        finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public PagedGridResult pageResume(ResumeSearchForm form) {
        //构建复合查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //标识是否传入查询条件
        boolean flag = false;
        //1 昵称  优势  技能点  证书
        if (StrUtils.isNotBlank(form.getBasicTitle())) {
            //传入条件
            flag = true;
            //构建查询条件
            BoolQueryBuilder basicQueryBuilder = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("nickName", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("advantage", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("skills", form.getBasicTitle()))
                    .should(QueryBuilders.matchQuery("credentials", form.getBasicTitle()));
            //将查询条件添加到复合查询中
            boolQueryBuilder.must(basicQueryBuilder);
        }
        //2 求职类型
        if (StrUtils.isNotBlank(form.getJobType())) {
            //传入条件
            flag = true;
            //将查询条件添加到复合查询中
            boolQueryBuilder.must(QueryBuilders.matchQuery("jobType", form.getJobType()));
        }
        //3 年龄
        if (form.getBeginAge() > 0 && form.getEndAge() > 0) {
            //传入条件
            flag = true;
            //将查询条件添加到复合查询中
            boolQueryBuilder.must(
                    QueryBuilders.rangeQuery("age").gte(form.getBeginAge()).lte(form.getEndAge())
            );
        }
        //4 性别
        if (form.getSex() == 1 || form.getSex() == 2) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.termQuery("sex", form.getSex()));
        }
        //5 活跃度
        if (form.getActiveTimes() > 0) {
            flag = true;
            //ES中的refreshTime字段比较  怎么比较? 最近三天活跃。  3*24*60*60*1000
            //刷新时间 >= 转为日期类型((当前时间 - 最近三天活跃的时间))
            //          三天前的时间     当前时间
            // |___________|______________|
            long mills = System.currentTimeMillis() - form.getActiveTimes();
            Date date = new Date(mills);
            boolQueryBuilder.must(
                    QueryBuilders.rangeQuery("refreshTime").gte(date)
            );
        }
        //6 工作经验
        if (form.getBeginWorkExpYears() > 0 && form.getEndWorkExpYears() > 0) {
            flag = true;
            boolQueryBuilder.must(
                    QueryBuilders.rangeQuery("workYears")
                            .gte(form.getBeginWorkExpYears()).lte(form.getEndWorkExpYears())
            );
        }
        //7 最高学历
        if (StrUtil.isNotBlank(form.getEdu())) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.termQuery("eduction", form.getEdu()));
        }
        //8 求职状态
        if (StrUtil.isNotBlank(form.getJobStatus())) {
            flag = true;
            boolQueryBuilder.must(QueryBuilders.termQuery("jobStatus", form.getJobStatus()));
        }
        //9 薪水范围
        if (form.getBeginSalary() > 0 && form.getEndSalary() > 0) {
            flag = true;
            BoolQueryBuilder must1 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.rangeQuery("beginSalary").lte(form.getBeginSalary()))
                    .must(QueryBuilders.rangeQuery("endSalary").gte(form.getBeginSalary()));

            BoolQueryBuilder must2 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.rangeQuery("beginSalary").gte(form.getBeginSalary()))
                    .must(QueryBuilders.rangeQuery("endSalary").lte(form.getEndSalary()));

            BoolQueryBuilder must3 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.rangeQuery("beginSalary").gte(form.getBeginSalary()))
                    .must(QueryBuilders.rangeQuery("endSalary").gte(form.getEndSalary()));

            boolQueryBuilder.should(must1).should(must2).should(must3);
        }
        //使用ES的API进行查询
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        if (flag) {
            //如果有查询条件按照复合查询的条件进行查询
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        } else {
            //如果没有查询条件,查询所有数据
            nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
        }
        //设置当前页 没有显示的记录数
        int page = (form.getPage() <= 0 || form.getPage() >= 50) ? 1 : form.getPage();
        //ES中第一页是从0开始
        Pageable pageable = PageRequest.of(page - 1, form.getSize());
        //查询数据
        Query query = nativeSearchQueryBuilder.withPageable(pageable).build();
        SearchHits<ResumeEo> searchHits = elasticsearchRestTemplate.search(query, ResumeEo.class);
        //获得总记录数
        long total = searchHits.getTotalHits();
        //将查询结果封装成分页数据返回
        List<ResumeEo> lists = searchHits.getSearchHits().stream()
                .map(hit -> hit.getContent())
                .collect(Collectors.toList());
        PagedGridResult pagedGridResult = new PagedGridResult();
        //当前页数
        pagedGridResult.setPage(page);
        //每页显示的记录数
        pagedGridResult.setPageSize(form.getSize());
        //当前页的数据
        pagedGridResult.setRows(lists);
        //总记录数
        pagedGridResult.setRecords(total);
        return pagedGridResult;
    }

    @Override
    public void refreshResume(String resumeId, String userId) {

        //删除简历信息
        redisUtil.delete(BaseInfoProperties.REDIS_RESUME_INFO+":"+resumeId);

        //将用户的简历信息封装到ResumeEo中,将ResumeEo对象保存到ES中。
        ResumeEo resumeEo = new ResumeEo();
        //根据用户的ID获得用户的信息
        Object object = usersFeignClient.detail(userId);
        String jsonStr = JSONUtil.toJsonStr(object);
        GraceJSONResult graceJSONResult = JSONUtil.toBean(jsonStr, GraceJSONResult.class);
        JSONObject jsonObject = (JSONObject) graceJSONResult.getData();
        Users user = jsonObject.toBean(Users.class);
        //设置用户的相关信息
        resumeEo.setBirthday(DateUtil.format(user.getBirthday(), "yyyy-MM-dd"));
        resumeEo.setAge(user.getAge());
        resumeEo.setUserId(user.getId());
        resumeEo.setNickName(user.getNickname());
        resumeEo.setSex(user.getSex());
        resumeEo.setFace(user.getFace());
        int workYears = user.getStartWorkDate() == null ? 0 : DateUtil.ageOfNow(user.getStartWorkDate());
        resumeEo.setWorkYears(workYears);
        //查询最新的工作经历
        List<ResumeWorkExp> workExps = resumeWorkExpService.queryByResumeId(resumeId, userId);
        if (CollUtil.isNotEmpty(workExps)) {
            //设置用户最近的工作经历相关信息
            ResumeWorkExp resumeWorkExp = workExps.get(0);
            resumeEo.setCompanyName(resumeWorkExp.getCompanyName());
            resumeEo.setPosition(resumeWorkExp.getPosition());
            resumeEo.setIndustry(resumeWorkExp.getIndustry());
        }
        //查询最高学历
        List<ResumeEducation> resumeEducations = resumeEducationService.queryResumeEducationByResumeId(resumeId, userId);
        if (CollUtil.isNotEmpty(resumeEducations)) {
            //设置用户最高学历相关信息
            ResumeEducation resumeEducation = resumeEducations.get(0);
            resumeEo.setEduction(resumeEducation.getEducation());
            resumeEo.setSchool(resumeEducation.getSchool());
            resumeEo.setMajor(resumeEducation.getMajor());
        }
        //查询求职期望(每个求职期望对应的就是ES中的一个简历的对象)
        List<ResumeExpect> resumeExpects = resumeExpectService.queryResumeExpectByResumeId(resumeId, userId);
        List<ResumeEo> resumeEos = new ArrayList<>();
        if (CollUtil.isNotEmpty(resumeExpects)) {
            resumeExpects.stream().forEach(expect -> {
                resumeEo.setResumeExpectId(expect.getId());
                resumeEo.setJobType(expect.getJobName());
                resumeEo.setBeginSalary(expect.getBeginSalary());
                resumeEo.setEndSalary(expect.getEndSalary());
                resumeEo.setCity(expect.getCity());
                //设置简历的最新收藏时间
                ResumeCollect resumeCollect = resumeCollectService.queryResumeCollect(expect.getId());
                if (resumeCollect != null) {
                    resumeEo.setHrCollectResumeTime(
                            DateUtil.format(resumeCollect.getCreatedTime(), "yyyy-MM-dd HH:mm:ss")
                    );
                }
                resumeEos.add(resumeEo);
            });
        }
        //查询简历信息
        LambdaQueryWrapper<Resume> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Resume::getId, resumeId);
        wrapper.eq(Resume::getUserId, userId);
        //设置个人优势相关信息
        Resume resume = getBaseMapper().selectOne(wrapper);
        resumeEo.setCredentials(resume.getCredentials());
        resumeEo.setSkills(resume.getSkills());
        resumeEo.setAdvantage(resume.getAdvantage());
        resumeEo.setAdvantageHTML(resume.getAdvantageHtml());
        resumeEo.setJobStatus(resume.getStatus());
        //设置简历刷新时间
        resumeEo.setRefreshTime(DateUtil.format(resume.getRefreshTime(), "yyyy-MM-dd"));
        //将简历信息保存到ES中
        IndexQuery indexQuery = new IndexQueryBuilder().withObject(resumeEos).build();
        //指定ES中的索引名称
        IndexCoordinates coordinates = IndexCoordinates.of("resume");
        //将所有的简历信息保存到ES中
        elasticsearchRestTemplate.save(resumeEos, coordinates);
    }

    @Transactional
    @Override
    public boolean initResume(String userId) {
        Resume resume = Resume.builder().userId(userId).build();
        return getBaseMapper().insert(resume) > 0;
    }

    @Override
    public boolean updateResume(ResumeUpdateForm form) {
        LambdaUpdateWrapper<Resume> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(Resume::getId, form.getId());
        //个人优势
        wrapper.set(Resume::getAdvantage, form.getAdvantage());
        wrapper.set(Resume::getAdvantageHtml, form.getAdvantageHtml());
        //设置资格证书
        wrapper.set(Resume::getCredentials, form.getCredentials());
        //技能标签
        wrapper.set(Resume::getSkills, form.getSkills());
        //求职状态
        wrapper.set(Resume::getStatus, form.getStatus());
        return update(wrapper);
    }


}